import abc
import sys
from typing import Iterator, TypeVar

from zkl_serialization import JsonValue

from jobs_agency.basics import AttachedJob, DetachedJob, JobsStore, Platform


class SkeletonAttachedJob(AttachedJob):
    def __init__(self):
        self._stopped = False
        self._detached = False

    @property
    def active(self) -> bool:
        return not self._stopped and not self._detached

    def detach(self):
        if self.active:
            self._detach()
            self._detached = True

    def terminate(self):
        if self.active:
            self._terminate()
            self._stopped = True

    @abc.abstractmethod
    def _detach(self):
        pass

    @abc.abstractmethod
    def _terminate(self):
        pass


class SkeletonDetachedJob(DetachedJob, abc.ABC):
    def __init__(self, id: str, platform: 'SkeletonPlatform'):
        self._id = id
        self._platform = platform
        self._active = True

    @property
    def id(self) -> str:
        return self._id

    @property
    def active(self) -> bool:
        if self._active:
            self._active = self._check_active()
            if not self._active:
                self._platform._store.remove_job(self.id)
        return self._active

    def monitor(self):
        if not self.active:
            print("The job has already stopped. Nothing to monitor.", file=sys.stderr)
            return
        self._monitor()

    def terminate(self):
        if not self.active:
            print("The job has already stopped.", file=sys.stderr)
            return
        self._terminate()
        self._active = False
        self._platform._store.remove_job(self.id)

    @abc.abstractmethod
    def _check_active(self) -> bool:
        pass

    @abc.abstractmethod
    def _monitor(self):
        pass

    @abc.abstractmethod
    def _terminate(self):
        pass


C = TypeVar('C')
P = TypeVar('P')
AJ = TypeVar('AJ', bound=SkeletonAttachedJob)
DJ = TypeVar('DJ', bound=SkeletonDetachedJob)


class SkeletonPlatform(Platform[C, P, AJ, DJ], abc.ABC):
    def __init__(self, store: JobsStore):
        self._store = store

    def new_attached_job(self, config: C) -> AJ:
        return self._new_attached_job(config)

    def new_detached_job(self, config: C, plan: P) -> DJ:
        return self._new_detached_job(config, plan)

    def get_detached_job(self, job_id: str) -> DJ | None:
        try:
            job_essence = self._store.load_job(job_id)
        except KeyError:
            return None

        job = self._restore_detached_job(job_id, job_essence)
        if job is None:
            return None

        if not job.active:
            self._store.remove_job(job_id)
            return None

        return job

    def iter_detached_jobs(self) -> Iterator[DJ]:
        for job_id in self._store.iter_jobs_id():
            job = self.get_detached_job(job_id)
            if job is not None:
                yield job

    @abc.abstractmethod
    def _new_attached_job(self, config: C) -> AJ:
        pass

    @abc.abstractmethod
    def _new_detached_job(self, config: C, plan: P) -> DJ:
        pass

    @abc.abstractmethod
    def _restore_detached_job(self, job_id: str, job_essence: JsonValue) -> DJ | None:
        pass
